﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Collections;


namespace LiveSequence.Suite.Helpers
{
  public static class CloneHelper<T> where T : class
  {
          /// <summary>
    /// Clone the object, and returning a reference to a cloned object.
    /// </summary>
    /// <returns>Reference to the new cloned 
    /// object.</returns>
    public static T Clone(object o)
    {
      if (o == null)
        return null;

      //First we create an instance of this specific type.
      object newObject = Activator.CreateInstance(o.GetType());

      //We get the array of fields for the new type instance.
      FieldInfo[] fields = newObject.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance);

      int i = 0;

      FieldInfo[] sourceFields = o.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
      foreach (FieldInfo fi in sourceFields)
      {
        //We query if the fiels support the ICloneable interface.
        Type ICloneType = fi.FieldType.
                    GetInterface("ICloneable", true);

        object fieldValue = fi.GetValue(o);
        object clonedValue = null;
        if (fieldValue == null)
        {
          clonedValue = null;
        }
        else if (ICloneType != null)
        {
  
          //Getting the ICloneable interface from the object.
          ICloneable IClone = (ICloneable)fieldValue;

          //We use the clone method to set the new value to the field.
          clonedValue = IClone.Clone();
        }
        else if (!fi.FieldType.IsPrimitive)
        {
          // If the field doesn't support the ICloneable 
          // interface then just set it.
 
          clonedValue = CloneHelper<object>.Clone(fieldValue);
        }
        else
        {
          clonedValue = fieldValue;
        }

        fields[i].SetValue(newObject, clonedValue);

        //Now we check if the object support the 
        //IEnumerable interface, so if it does
        //we need to enumerate all its items and check if 
        //they support the ICloneable interface.
        Type IEnumerableType = fi.FieldType.GetInterface
                        ("IEnumerable", true);
        if (IEnumerableType != null)
        {
          //Get the IEnumerable interface from the field.
          IEnumerable IEnum = (IEnumerable)fi.GetValue(o);

          //This version support the IList and the 
          //IDictionary interfaces to iterate on collections.
          Type IListType = fields[i].FieldType.GetInterface
                              ("IList", true);
          Type IDicType = fields[i].FieldType.GetInterface
                              ("IDictionary", true);

          int j = 0;
          if (IListType != null)
          {
            //Getting the IList interface.
            IList list = (IList)fields[i].GetValue(newObject);

            foreach (object item in IEnum)
            {
              if (item != null)
              {
                //Checking to see if the current item 
                //support the ICloneable interface.
                ICloneType = item.GetType().
                    GetInterface("ICloneable", true);

                if (ICloneType != null)
                {
                  //If it does support the ICloneable interface, 
                  //we use it to set the clone of
                  //the object in the list.
                  ICloneable clone = (ICloneable)item;

                  list[j] = clone.Clone();
                }
                else
                {
                  object clonedItem = CloneHelper<object>.Clone(item);
                  list[j] = clonedItem;
                }
              }
              else
                list[j] = null;
              //NOTE: If the item in the list is not 
              //support the ICloneable interface then in the 
              //cloned list this item will be the same 
              //item as in the original list
              //(as long as this type is a reference type).

              j++;
            }
          }
          else if (IDicType != null)
          {
            //Getting the dictionary interface.
            IDictionary dic = (IDictionary)fields[i].
                                GetValue(newObject);
            j = 0;

            foreach (DictionaryEntry de in IEnum)
            {
              //Checking to see if the item 
              //support the ICloneable interface.
              ICloneType = de.Value.GetType().
                  GetInterface("ICloneable", true);

              if (ICloneType != null)
              {
                ICloneable clone = (ICloneable)de.Value;

                dic[de.Key] = clone.Clone();
              }
              j++;
            }
          }
        }
        i++;
      }
      return newObject as T;
    }
  }
}
